using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PropertiesExtractor.Targets;
using System.IO;
using Microsoft.Build.Utilities;
using ICSharpCode.NRefactory.Visitors;
using ICSharpCode.NRefactory.Ast;
using ICSharpCode.NRefactory;
using PropertiesExtractor.Tests.Data;

namespace PropertiesExtractor.Tests
{
	[TestClass]
	public class PropertiesExtractorTaskTest
	{
		static TestVisitor Generated;
		static String[] InputFiles = new[] { "Classes1.cs", "PartialClass2.cs", "Interface1.cs" };
		static Type[] InputTypes = new[] { typeof(Class1), typeof(Class2), typeof(PartialClass2), typeof(Interface1) };

		[ClassInitialize]
		public static void Init(TestContext context)
		{
			PropertiesExtractorTask task = new PropertiesExtractorTask();
			task.OutputDir = Directory.GetCurrentDirectory();
			task.Inputs = InputFiles.Select(fileName => new TaskItem(@"..\..\..\PropertiesExtractor.Tests\Data\" + fileName)).ToArray();
			Assert.IsTrue(task.Execute(), "PropertiesExtractorTask should success");
			Generated = new TestVisitor(task.Outputs.Select(file => new FileInfo(file.ItemSpec)).ToArray());
		}
		[TestMethod]
		public void AssertMetadataFilesAreCreated()
		{
			foreach(var file in InputFiles)
			{
				var expectedGeneratedFileName = file + ".g.Metadata.cs";
				var hasExpected = Generated.GeneratedFiles.Any(fi => fi.Name == expectedGeneratedFileName);
				Assert.IsTrue(hasExpected, "expected file " + expectedGeneratedFileName + " not created");
			}
		}

		[TestMethod]
		public void AssertTypesAreCreatedAndHasSameVisibility()
		{
			foreach(var inputType in InputTypes)
			{
				string expectedTypeName = GetMetadataName(inputType.Name);
				var actualClass = Generated.AssertClassExist(expectedTypeName);
				var visibility = actualClass.Modifier & (Modifiers)15;
				if(inputType.IsPublic)
				{
					Assert.IsTrue(visibility == Modifiers.Public);
				}
				else
				{
					Assert.IsTrue(visibility == Modifiers.Internal || visibility == Modifiers.None);
				}
			}
		}


		[TestMethod]
		public void AssertMetadataOnPropertiesAreCreated()
		{
			foreach(var inputType in InputTypes)
			{
				var generatedInputTypes = Generated.GeneratedTypes.Where(t => t.Name == GetMetadataName(inputType.Name)); //If throw test AssertTypesAreCreated should fails
				foreach(var property in inputType.GetProperties())
				{
					var hasPropertyGenerated = generatedInputTypes.SelectMany(typeDecl => typeDecl.Children).OfType<PropertyDeclaration>().Any(decl => decl.Name == property.Name);
					Assert.IsTrue(hasPropertyGenerated, "property " + property.Name + " not generated for type " + property.ReflectedType.Name);
				}
			}
		}

		private string GetMetadataName(string typeName)
		{
			return typeName + "Properties";
		}
	}

	public class TestVisitor : AbstractAstVisitor
	{
		private readonly FileInfo[] _GeneratedFiles;
		public FileInfo[] GeneratedFiles
		{
			get
			{
				return _GeneratedFiles;
			}
		}
		public TestVisitor(FileInfo[] generatedFiles)
		{
			_GeneratedFiles = generatedFiles;
		}
		TypeDeclaration[] _GeneratedTypes;
		public TypeDeclaration[] GeneratedTypes
		{
			get
			{
				if(_GeneratedTypes == null)
				{
					_GeneratedTypes = ParseFiles().ToArray();
				}
				return _GeneratedTypes;
			}
		}

		public TypeDeclaration AssertClassExist(string className)
		{
			var actualClass = this.GeneratedTypes.FirstOrDefault(t => t.Name == className);
			Assert.IsTrue(actualClass != null, "expected class " + className + " not created");
			return actualClass;
		}

		private IEnumerable<TypeDeclaration> ParseFiles()
		{
			List<TypeDeclaration> result = new List<TypeDeclaration>();
			foreach(var file in GeneratedFiles)
			{
				var parser = ParserFactory.CreateParser(file.FullName);
				parser.Parse();
				if(parser.Errors.Count > 0)
					Assert.Fail("compilation failed for " + file.FullName + " : " + parser.Errors.ErrorOutput);
				parser.CompilationUnit.AcceptVisitor(this, result);
			}
			return result;
		}

		public override object VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data)
		{
			var result = (List<TypeDeclaration>)data;
			result.Add(typeDeclaration);
			return base.VisitTypeDeclaration(typeDeclaration, data);
		}
	}
}
